using System;
using FluentParser.Expressions;

namespace FluentParser.Expressions.Terminals.Parsing
{
    /// <summary>
    /// Expression reading a string using a predicate to verify the validity of
    /// each character.
    /// </summary>
    public class ParseFilterExpressionTerminal : Expr<string> 
    {
        public int? MinimumCharacters { get; private set; }
        public int? MaximumCharacters { get; private set; }
        public Func<char, bool> FirstCharacterPredicate { get; private set; }
        public Func<char, bool> OtherCharactersFilter { get; private set; }

        public ParseFilterExpressionTerminal(Func<char, bool> firstCharacter, Func<char, bool> otherCharacters, int? minimum, int? maximum)
        {
            FirstCharacterPredicate = firstCharacter;
            OtherCharactersFilter = otherCharacters;
            MinimumCharacters = minimum;
            MaximumCharacters = maximum;
            TypeOperation = OperationType.Parsing;
        }

        public override ParsingResult<string> Parse(GrammarContext context, int index)
        {
            string text = context.Text;
            int position = index;
            int maxPosition = text.Length;
            if (MaximumCharacters.HasValue && MaximumCharacters.Value < maxPosition - index )
                maxPosition = MaximumCharacters.Value + index;

            if (position < maxPosition && FirstCharacterPredicate(text[position]))
            {
                position++;

                while (position < maxPosition && OtherCharactersFilter(text[position]))
                    position++;
            }

            int length = position - index;

            if (!MinimumCharacters.HasValue || length >= MinimumCharacters.Value)
                return new ParsingResult<string>(index, length, text.Substring(index, length));
            return null;
        }

        public override void ParseNode(GrammarContext context, ParsingNodeResult node)
        {
            string text = context.Text;
            int position = node.Index;
            int maxPosition = text.Length;
            if (MaximumCharacters.HasValue && MaximumCharacters.Value < maxPosition - node.Index)
                maxPosition = MaximumCharacters.Value + node.Index;

            if (position < maxPosition && FirstCharacterPredicate(text[position]))
            {
                position++;

                while (position < maxPosition && OtherCharactersFilter(text[position]))
                    position++;
            }

            int length = position - node.Index;

            if (!MinimumCharacters.HasValue || length >= MinimumCharacters.Value)
            {
                node.Length = length;
                node.Value = text.Substring(node.Index, length);
            }

            if (maxPosition != position)
                length++;

            node.ExploredLength = length;
        }
    }
}